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

Commit 91464139 authored by Wale Ogunwale's avatar Wale Ogunwale Committed by Android (Google) Code Review
Browse files

Merge "drag-n-drop support for main/side stage split-screen"

parents 7a52ee08 a47141f5
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -100,7 +100,7 @@ public class ShellInitImpl {
        mSplitScreenOptional.ifPresent(SplitScreen::onOrganizerRegistered);

        // Bind the splitscreen impl to the drag drop controller
        mDragAndDropController.initialize(mLegacySplitScreenOptional);
        mDragAndDropController.initialize(mSplitScreenOptional);

        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
            mTransitions.register(mShellTaskOrganizer);
+1 −1
Original line number Diff line number Diff line
@@ -135,7 +135,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {

                final int position = mSplitLayout.getDividePosition() + touchPos - mStartPos;
                final DividerSnapAlgorithm.SnapTarget snapTarget =
                        mSplitLayout.findSnapTarget(position, velocity);
                        mSplitLayout.findSnapTarget(position, velocity, false /* hardDismiss */);
                mSplitLayout.snapToTarget(position, snapTarget);
                break;
        }
+6 −4
Original line number Diff line number Diff line
@@ -188,11 +188,11 @@ public class SplitLayout {
    public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
        switch (snapTarget.flag) {
            case FLAG_DISMISS_START:
                mLayoutChangeListener.onSnappedToDismiss(false /* snappedToEnd */);
                mLayoutChangeListener.onSnappedToDismiss(false /* bottomOrRight */);
                mSplitWindowManager.setResizingSplits(false);
                break;
            case FLAG_DISMISS_END:
                mLayoutChangeListener.onSnappedToDismiss(true /* snappedToEnd */);
                mLayoutChangeListener.onSnappedToDismiss(true /* bottomOrRight */);
                mSplitWindowManager.setResizingSplits(false);
                break;
            default:
@@ -207,9 +207,11 @@ public class SplitLayout {

    /**
     * Returns {@link DividerSnapAlgorithm.SnapTarget} which matches passing position and velocity.
     * If hardDismiss is set to {@code true}, it will be harder to reach dismiss target.
     */
    public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity) {
        return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity);
    public DividerSnapAlgorithm.SnapTarget findSnapTarget(int position, float velocity,
            boolean hardDismiss) {
        return mDividerSnapAlgorithm.calculateSnapTarget(position, velocity, hardDismiss);
    }

    private DividerSnapAlgorithm getSnapAlgorithm(Resources resources, Rect rootBounds) {
+5 −5
Original line number Diff line number Diff line
@@ -52,7 +52,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;

import java.util.Optional;

@@ -66,7 +66,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange

    private final Context mContext;
    private final DisplayController mDisplayController;
    private LegacySplitScreen mLegacySplitScreen;
    private SplitScreen mSplitScreen;

    private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
@@ -76,8 +76,8 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
        mDisplayController = displayController;
    }

    public void initialize(Optional<LegacySplitScreen> splitscreen) {
        mLegacySplitScreen = splitscreen.orElse(null);
    public void initialize(Optional<SplitScreen> splitscreen) {
        mSplitScreen = splitscreen.orElse(null);
        mDisplayController.addDisplayWindowListener(this);
    }

@@ -104,7 +104,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
                R.layout.global_drop_target, null);
        rootView.setOnDragListener(this);
        rootView.setVisibility(View.INVISIBLE);
        DragLayout dragLayout = new DragLayout(context, mLegacySplitScreen);
        DragLayout dragLayout = new DragLayout(context, mSplitScreen);
        rootView.addView(dragLayout,
                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        try {
+87 −121
Original line number Diff line number Diff line
@@ -34,6 +34,8 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.splitscreen.SplitScreen.SIDE_STAGE_POSITION_TOP_OR_LEFT;

import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -59,13 +61,12 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.splitscreen.SplitScreen;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

/**
 * The policy for handling drag and drop operations to shell.
@@ -77,22 +78,22 @@ public class DragAndDropPolicy {
    private final Context mContext;
    private final ActivityTaskManager mActivityTaskManager;
    private final Starter mStarter;
    private final LegacySplitScreen mLegacySplitScreen;
    private final SplitScreen mSplitScreen;
    private final ArrayList<DragAndDropPolicy.Target> mTargets = new ArrayList<>();

    private DragSession mSession;

    public DragAndDropPolicy(Context context, LegacySplitScreen legacySplitScreen) {
        this(context, ActivityTaskManager.getInstance(), legacySplitScreen,
                new DefaultStarter(context, legacySplitScreen));
    public DragAndDropPolicy(Context context, SplitScreen splitScreen) {
        this(context, ActivityTaskManager.getInstance(), splitScreen,
                new DefaultStarter(context, splitScreen));
    }

    @VisibleForTesting
    DragAndDropPolicy(Context context, ActivityTaskManager activityTaskManager,
            LegacySplitScreen legacySplitScreen, Starter starter) {
            SplitScreen splitScreen, Starter starter) {
        mContext = context;
        mActivityTaskManager = activityTaskManager;
        mLegacySplitScreen = legacySplitScreen;
        mSplitScreen = splitScreen;
        mStarter = starter;
    }

@@ -122,64 +123,54 @@ public class DragAndDropPolicy {
        final int ih = h - insets.top - insets.bottom;
        final int l = insets.left;
        final int t = insets.top;
        final boolean isVerticalSplit = mSession.isPhone && !mSession.displayLayout.isLandscape();
        if (mSession.dragItemSupportsSplitscreen
                && mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
                && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN
                && mSession.runningTaskIsResizeable) {
            // Allow splitting when there is a fullscreen standard activity running
            if (isVerticalSplit) {
                // TODO(b/169894807): For now, only allow splitting to the right/bottom until we
                //                    have split pairs
                mTargets.add(new Target(TYPE_FULLSCREEN,
                        new Rect(l, t, l + iw, t + ih / 2),
                        new Rect(l, t, l + iw, t + ih),
                        new Rect(0, 0, w, h)));
                mTargets.add(new Target(TYPE_SPLIT_BOTTOM,
                        new Rect(l, t + ih / 2, l + iw, t + ih),
                        new Rect(l, t + ih / 2, l + iw, t + ih),
                        new Rect(0, h / 2, w, h)));
            } else {
                mTargets.add(new Target(TYPE_FULLSCREEN,
                        new Rect(l, t, l + iw / 2, t + ih),
                        new Rect(l, t, l + iw, t + ih),
                        new Rect(0, 0, w, h)));
                mTargets.add(new Target(TYPE_SPLIT_RIGHT,
                        new Rect(l + iw / 2, t, l + iw, t + ih),
                        new Rect(l + iw / 2, t, l + iw, t + ih),
                        new Rect(w / 2, 0, w, h)));
            }
        } else if (mSession.dragItemSupportsSplitscreen
                && mLegacySplitScreen != null
                && mLegacySplitScreen.isDividerVisible()) {
        final Rect displayRegion = new Rect(l, t, l + iw, t + ih);
        final Rect fullscreenDrawRegion = new Rect(displayRegion);
        final Rect fullscreenHitRegion = new Rect(displayRegion);
        final boolean inLandscape = mSession.displayLayout.isLandscape();
        final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
        // We allow splitting if we are already in split-screen or the running task is a standard
        // task in fullscreen mode.
        final boolean allowSplit = inSplitScreen
                || (mSession.runningTaskActType == ACTIVITY_TYPE_STANDARD
                        && mSession.runningTaskWinMode == WINDOWING_MODE_FULLSCREEN);
        if (allowSplit) {
            // Already split, allow replacing existing split task
            // TODO(b/169894807): For now, only allow replacing the non-primary task until we have
            //                    split pairs
            final Rect secondarySplitRawBounds =
                    mLegacySplitScreen.getDividerView().getNonMinimizedSplitScreenSecondaryBounds();
            final Rect secondarySplitBounds = new Rect(secondarySplitRawBounds);
            secondarySplitBounds.intersect(new Rect(l, t, l + iw, t + ih));
            if (isVerticalSplit) {
                mTargets.add(new Target(TYPE_FULLSCREEN,
                        new Rect(l, t, l + iw, secondarySplitRawBounds.top),
                        new Rect(l, t, l + iw, t + ih),
                        new Rect(0, 0, w, secondarySplitRawBounds.top)));
            final Rect topOrLeftBounds = new Rect();
            final Rect bottomOrRightBounds = new Rect();
            mSplitScreen.getStageBounds(topOrLeftBounds, bottomOrRightBounds);
            topOrLeftBounds.intersect(displayRegion);
            bottomOrRightBounds.intersect(displayRegion);

            if (inLandscape) {
                final Rect leftHitRegion = new Rect();
                final Rect leftDrawRegion = topOrLeftBounds;
                final Rect rightHitRegion = new Rect();
                final Rect rightDrawRegion = bottomOrRightBounds;

                displayRegion.splitVertically(leftHitRegion, fullscreenHitRegion, rightHitRegion);

                mTargets.add(
                        new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
                mTargets.add(new Target(TYPE_SPLIT_LEFT, leftHitRegion, leftDrawRegion));
                mTargets.add(new Target(TYPE_SPLIT_RIGHT, rightHitRegion, rightDrawRegion));

            } else {
                mTargets.add(new Target(TYPE_FULLSCREEN,
                        new Rect(l, t, secondarySplitRawBounds.left, t + ih),
                        new Rect(l, t, l + iw, t + ih),
                        new Rect(0, 0, w, h)));
            }
            mTargets.add(new Target(isVerticalSplit ? TYPE_SPLIT_BOTTOM : TYPE_SPLIT_RIGHT,
                    new Rect(secondarySplitBounds),
                    new Rect(secondarySplitBounds),
                    new Rect(secondarySplitBounds)));
                final Rect topHitRegion = new Rect();
                final Rect topDrawRegion = topOrLeftBounds;
                final Rect bottomHitRegion = new Rect();
                final Rect bottomDrawRegion = bottomOrRightBounds;

                displayRegion.splitHorizontally(
                        topHitRegion, fullscreenHitRegion, bottomHitRegion);

                mTargets.add(
                        new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
                mTargets.add(new Target(TYPE_SPLIT_TOP, topHitRegion, topDrawRegion));
                mTargets.add(new Target(TYPE_SPLIT_BOTTOM, bottomHitRegion, bottomDrawRegion));
            }
        } else {
            // Otherwise only show the fullscreen target
            mTargets.add(new Target(TYPE_FULLSCREEN,
                    new Rect(l, t, l + iw, t + ih),
                    new Rect(l, t, l + iw, t + ih),
                    new Rect(0, 0, w, h)));
            // Split-screen not allowed, so only show the fullscreen target
            mTargets.add(new Target(TYPE_FULLSCREEN, fullscreenHitRegion, fullscreenDrawRegion));
        }
        return mTargets;
    }
@@ -208,32 +199,26 @@ public class DragAndDropPolicy {
        final boolean isTask = description.hasMimeType(MIMETYPE_APPLICATION_TASK);
        final boolean isShortcut = description.hasMimeType(MIMETYPE_APPLICATION_SHORTCUT);
        final Intent dragData = mSession.dragData;
        final boolean inSplitScreen = mSplitScreen != null && mSplitScreen.isSplitScreenVisible();
        final boolean leftOrTop = target.type == TYPE_SPLIT_TOP || target.type == TYPE_SPLIT_LEFT;
        final Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
                ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
                : new Bundle();

        boolean deferAppLaunchUntilSplit = false;
        if (target.type == TYPE_FULLSCREEN) {
            if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) {
                // If in split, remove split and launch fullscreen
                mStarter.exitSplitScreen(mSession.runningTaskId);
            } else {
                // Not in split, fall through to launch
            }
        } else {
            if (mLegacySplitScreen != null && mLegacySplitScreen.isDividerVisible()) {
                // Split is already visible, just replace the task
                // TODO(b/169894807): Since we only allow replacing the non-primary target above
                //                    just fall through and start the activity
            } else {
                // Not in split, enter split now
                mStarter.enterSplitScreen(mSession.runningTaskId,
                        target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP);
                deferAppLaunchUntilSplit = true;
            // Exit split stages if needed
            mStarter.exitSplitScreen();
        } else if (mSplitScreen != null) {
            // Update launch options for the split side we are targeting.
            final int position = leftOrTop
                    ? SIDE_STAGE_POSITION_TOP_OR_LEFT : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT;
            if (!inSplitScreen) {
                // Update the side stage position to match where we want to launch.
                mSplitScreen.setSideStagePosition(position);
            }
            mSplitScreen.updateActivityOptions(opts, position);
        }

        final Runnable startAppRunnable = () -> {
            Bundle opts = dragData.hasExtra(EXTRA_ACTIVITY_OPTIONS)
                    ? dragData.getBundleExtra(EXTRA_ACTIVITY_OPTIONS)
                    : null;
        if (isTask) {
            mStarter.startTask(dragData.getIntExtra(EXTRA_TASK_ID, INVALID_TASK_ID), opts);
        } else if (isShortcut) {
@@ -243,24 +228,6 @@ public class DragAndDropPolicy {
        } else {
            mStarter.startIntent(dragData.getParcelableExtra(EXTRA_PENDING_INTENT), opts);
        }
        };
        if (deferAppLaunchUntilSplit) {
            // TODO(b/169894807): The enterSplitScreen() call above will trigger the current task
            // into split, and we should wait for home and other tasks to be moved to
            // split-secondary before trying to launch the new secondary task.  This can be removed
            // once we have app-pairs.
            mLegacySplitScreen.registerInSplitScreenListener(new Consumer<Boolean>() {
                @Override
                public void accept(Boolean inSplit) {
                    if (inSplit) {
                        startAppRunnable.run();
                        mLegacySplitScreen.unregisterInSplitScreenListener(this);
                    }
                }
            });
        } else {
            startAppRunnable.run();
        }
    }

    /**
@@ -323,7 +290,7 @@ public class DragAndDropPolicy {
                UserHandle user);
        void startIntent(PendingIntent intent, Bundle activityOptions);
        void enterSplitScreen(int taskId, boolean leftOrTop);
        void exitSplitScreen(int taskId);
        void exitSplitScreen();
    }

    /**
@@ -332,17 +299,17 @@ public class DragAndDropPolicy {
     */
    private static class DefaultStarter implements Starter {
        private final Context mContext;
        private final LegacySplitScreen mLegacySplitScreen;
        private final SplitScreen mSplitScreen;

        public DefaultStarter(Context context, LegacySplitScreen legacySplitScreen) {
        public DefaultStarter(Context context, SplitScreen splitScreen) {
            mContext = context;
            mLegacySplitScreen = legacySplitScreen;
            mSplitScreen = splitScreen;
        }

        @Override
        public void startTask(int taskId, Bundle activityOptions) {
            try {
                ActivityTaskManager.getService().startActivityFromRecents(taskId, null);
                ActivityTaskManager.getService().startActivityFromRecents(taskId, activityOptions);
            } catch (RemoteException e) {
                Slog.e(TAG, "Failed to launch task", e);
            }
@@ -372,12 +339,14 @@ public class DragAndDropPolicy {

        @Override
        public void enterSplitScreen(int taskId, boolean leftOrTop) {
            mLegacySplitScreen.splitPrimaryTask();
            mSplitScreen.moveToSideStage(taskId,
                    leftOrTop ? SIDE_STAGE_POSITION_TOP_OR_LEFT
                            : SIDE_STAGE_POSITION_BOTTOM_OR_RIGHT);
        }

        @Override
        public void exitSplitScreen(int taskId) {
            mLegacySplitScreen.dismissSplitToPrimaryTask();
        public void exitSplitScreen() {
            mSplitScreen.exitSplitScreen();
        }
    }

@@ -406,19 +375,16 @@ public class DragAndDropPolicy {
        final Rect hitRegion;
        // The approximate visual region for where the task will start
        final Rect drawRegion;
        // The
        final Rect dropTargetBounds;

        public Target(@Type int t, Rect hit, Rect draw, Rect drop) {
        public Target(@Type int t, Rect hit, Rect draw) {
            type = t;
            hitRegion = hit;
            drawRegion = draw;
            dropTargetBounds = drop;
        }

        @Override
        public String toString() {
            return "Target {hit=" + hitRegion + " drop=" + dropTargetBounds + "}";
            return "Target {hit=" + hitRegion + " draw=" + drawRegion + "}";
        }
    }
}
Loading