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

Commit dc4f41d4 authored by Vinit Nayak's avatar Vinit Nayak Committed by Android (Google) Code Review
Browse files

Merge "Add support for dragging third app on top of 2 app split" into main

parents 1eea88f1 446b9ea7
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -170,7 +170,7 @@ public class SplitScreenConstants {
            SNAP_TO_NONE,
            SNAP_TO_START_AND_DISMISS,
            SNAP_TO_END_AND_DISMISS,
            SNAP_TO_MINIMIZE
            SNAP_TO_MINIMIZE,
    })
    public @interface SnapPosition {}

+158 −31
Original line number Diff line number Diff line
@@ -23,6 +23,7 @@ import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;

import static com.android.wm.shell.Flags.enableFlexibleSplit;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_BOTTOM;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_LEFT;
import static com.android.wm.shell.draganddrop.SplitDragPolicy.Target.TYPE_SPLIT_RIGHT;
@@ -43,14 +44,18 @@ import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.window.WindowContainerToken;

@@ -67,14 +72,22 @@ import com.android.wm.shell.shared.animation.Interpolators;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;

/**
 * Coordinates the visible drop targets for the current drag within a single display.
 */
public class DragLayout extends LinearLayout
        implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider {
        implements ViewTreeObserver.OnComputeInternalInsetsListener, DragLayoutProvider,
        DragZoneAnimator{

    static final boolean DEBUG_LAYOUT = false;
    // While dragging the status bar is hidden.
    private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
            | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
@@ -108,13 +121,19 @@ public class DragLayout extends LinearLayout
    // The last position that was handled by the drag layout
    private final Point mLastPosition = new Point();

    // Used with enableFlexibleSplit() flag
    private List<SplitDragPolicy.Target> mTargets;
    private Map<SplitDragPolicy.Target, DropZoneView> mTargetDropMap = new HashMap<>();
    private FrameLayout mAnimatingRootLayout;
    // Used with enableFlexibleSplit() flag

    @SuppressLint("WrongConstant")
    public DragLayout(Context context, SplitScreenController splitScreenController,
            IconProvider iconProvider) {
        super(context);
        mSplitScreenController = splitScreenController;
        mIconProvider = iconProvider;
        mPolicy = new SplitDragPolicy(context, splitScreenController);
        mPolicy = new SplitDragPolicy(context, splitScreenController, this);
        mStatusBarManager = context.getSystemService(StatusBarManager.class);
        mLastConfiguration.setTo(context.getResources().getConfiguration());

@@ -211,12 +230,27 @@ public class DragLayout extends LinearLayout
        boolean isLeftRightSplit = mSplitScreenController != null
                && mSplitScreenController.isLeftRightSplit();
        if (isLeftRightSplit) {
            if (enableFlexibleSplit()) {
                mTargetDropMap.values().forEach(dzv -> dzv.setBottomInset(mInsets.bottom));
            } else {
                mDropZoneView1.setBottomInset(mInsets.bottom);
                mDropZoneView2.setBottomInset(mInsets.bottom);
            }
        } else {
            if (enableFlexibleSplit()) {
                Collection<DropZoneView> dropViews = mTargetDropMap.values();
                final DropZoneView[] bottomView = {null};
                dropViews.forEach(dropZoneView -> {
                    bottomView[0] = dropZoneView;
                    dropZoneView.setBottomInset(0);
                });
                // TODO(b/349828130): necessary? maybe with UI polish
                //  bottomView[0].setBottomInset(mInsets.bottom);
            } else {
                mDropZoneView1.setBottomInset(0);
                mDropZoneView2.setBottomInset(mInsets.bottom);
            }
        }
        return super.onApplyWindowInsets(insets);
    }

@@ -233,18 +267,32 @@ public class DragLayout extends LinearLayout
        final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
                || (diff & CONFIG_UI_MODE) != 0;
        if (themeChanged) {
            if (enableFlexibleSplit()) {
                mTargetDropMap.values().forEach(DropZoneView::onThemeChange);
            } else {
                mDropZoneView1.onThemeChange();
                mDropZoneView2.onThemeChange();
            }
        }
        mLastConfiguration.setTo(newConfig);
        requestLayout();
    }

    private void updateContainerMarginsForSingleTask() {
        if (enableFlexibleSplit()) {
            DropZoneView firstDropZone = mTargetDropMap.values().stream().findFirst().get();
            mTargetDropMap.values().stream()
                    .filter(dropZoneView -> dropZoneView != firstDropZone)
                    .forEach(dropZoneView -> dropZoneView.setContainerMargin(0, 0, 0, 0));
            firstDropZone.setContainerMargin(
                    mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin
            );
        } else {
            mDropZoneView1.setContainerMargin(
                    mDisplayMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
            mDropZoneView2.setContainerMargin(0, 0, 0, 0);
        }
    }

    private void updateContainerMargins(boolean isLeftRightSplit) {
        final float halfMargin = mDisplayMargin / 2f;
@@ -305,6 +353,21 @@ public class DragLayout extends LinearLayout
                    updateContainerMarginsForSingleTask();
                }
            }
        } else {
            ActivityManager.RunningTaskInfo[] taskInfos = mSplitScreenController.getAllTaskInfos();
            boolean anyTasksNull = Arrays.stream(taskInfos).anyMatch(Objects::isNull);
            if (enableFlexibleSplit() && taskInfos != null && !anyTasksNull) {
                int i = 0;
                for (DropZoneView v : mTargetDropMap.values()) {
                    if (i >= taskInfos.length) {
                        // TODO(b/349828130) Support once we add 3 StageRoots
                        continue;
                    }
                    ActivityManager.RunningTaskInfo task = taskInfos[i];
                    v.setAppInfo(getResizingBackgroundColor(task),
                            mIconProvider.getIcon(task.topActivityInfo));
                    i++;
                }
            } else {
                // We're already in split so get taskInfo from the controller to populate icon / color.
                ActivityManager.RunningTaskInfo topOrLeftTask =
@@ -320,6 +383,7 @@ public class DragLayout extends LinearLayout
                    mDropZoneView1.setAppInfo(topOrLeftColor, topOrLeftIcon);
                    mDropZoneView2.setAppInfo(bottomOrRightColor, bottomOrRightIcon);
                }
            }

            // Update the dropzones to match existing split sizes
            Rect topOrLeftBounds = new Rect();
@@ -391,7 +455,14 @@ public class DragLayout extends LinearLayout
    @NonNull
    @Override
    public void addDraggingView(ViewGroup rootView) {
        // TODO(b/349828130) We need to separate out view + logic here
        if (enableFlexibleSplit()) {
            removeAllViews();
            mAnimatingRootLayout = new FrameLayout(getContext());
            addView(mAnimatingRootLayout,
                    new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            ((LayoutParams) mAnimatingRootLayout.getLayoutParams()).weight = 1;
        }

        rootView.addView(this, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

@@ -409,6 +480,24 @@ public class DragLayout extends LinearLayout
            // Inset the draw region by a little bit
            target.drawRegion.inset(mDisplayMargin, mDisplayMargin);
        }

        if (enableFlexibleSplit()) {
            mTargets = targets;
            mTargetDropMap.clear();
            for (int i = 0; i < mTargets.size(); i++) {
                DropZoneView v = new DropZoneView(getContext());
                SplitDragPolicy.Target t = mTargets.get(i);
                FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(t.drawRegion.width(),
                        t.drawRegion.height());
                mAnimatingRootLayout.addView(v, params);
                v.setTranslationX(t.drawRegion.left);
                v.setTranslationY(t.drawRegion.top);
                mTargetDropMap.put(t, v);
                if (DEBUG_LAYOUT) {
                    v.setDebugIndex(t.index);
                }
            }
        }
    }

    /**
@@ -433,6 +522,9 @@ public class DragLayout extends LinearLayout
            if (target == null) {
                // Animating to no target
                animateSplitContainers(false, null /* animCompleteCallback */);
                if (enableFlexibleSplit()) {
                    animateHighlight(target);
                }
            } else if (mCurrentTarget == null) {
                if (mPolicy.getNumTargets() == 1) {
                    animateFullscreenContainer(true);
@@ -440,10 +532,14 @@ public class DragLayout extends LinearLayout
                    animateSplitContainers(true, null /* animCompleteCallback */);
                    animateHighlight(target);
                }
            } else if (mCurrentTarget.type != target.type) {
            } else if (mCurrentTarget.type != target.type || enableFlexibleSplit()) {
                // Switching between targets
                if (enableFlexibleSplit()) {
                    animateHighlight(target);
                } else {
                    mDropZoneView1.animateSwitch();
                    mDropZoneView2.animateSwitch();
                }
                // Announce for accessibility.
                switch (target.type) {
                    case TYPE_SPLIT_LEFT:
@@ -490,6 +586,9 @@ public class DragLayout extends LinearLayout
        mDropZoneView2.setForceIgnoreBottomMargin(false);
        updateContainerMargins(mIsLeftRightSplit);
        mCurrentTarget = null;
        if (enableFlexibleSplit()) {
            mAnimatingRootLayout.removeAllViews();
        }
    }

    /**
@@ -566,9 +665,20 @@ public class DragLayout extends LinearLayout
        mStatusBarManager.disable(visible
                ? HIDE_STATUS_BAR_FLAGS
                : DISABLE_NONE);
        Animator animator;
        if (enableFlexibleSplit()) {
            DropZoneView anyDropZoneView = null;
            for (DropZoneView dz : mTargetDropMap.values()) {
                dz.setShowingMargin(visible);
                anyDropZoneView = dz;
            }
            animator = anyDropZoneView != null ? anyDropZoneView.getAnimator() : null;
        } else {
            mDropZoneView1.setShowingMargin(visible);
            mDropZoneView2.setShowingMargin(visible);
        Animator animator = mDropZoneView1.getAnimator();
            animator = mDropZoneView1.getAnimator();
        }

        if (animCompleteCallback != null) {
            if (animator != null) {
                animator.addListener(new AnimatorListenerAdapter() {
@@ -584,7 +694,24 @@ public class DragLayout extends LinearLayout
        }
    }

    @Override
    public void animateDragTargets(
            @NonNull List<? extends BiConsumer<SplitDragPolicy.Target, View>> viewsToAnimate) {
        for (Map.Entry<SplitDragPolicy.Target, DropZoneView> entry : mTargetDropMap.entrySet()) {
            viewsToAnimate.get(0).accept(entry.getKey(), entry.getValue());
        }
    }

    private void animateHighlight(SplitDragPolicy.Target target) {
        if (enableFlexibleSplit()) {
            for (Map.Entry<SplitDragPolicy.Target, DropZoneView> dzv : mTargetDropMap.entrySet()) {
                // Highlight the view w/ the matching target, unhighlight the rest
                dzv.getValue().setShowingHighlight(dzv.getKey() == target);
            }
            mPolicy.onHoveringOver(target);
            return;
        }

        if (target.type == TYPE_SPLIT_LEFT || target.type == TYPE_SPLIT_TOP) {
            mDropZoneView1.setShowingHighlight(true);
            mDropZoneView2.setShowingHighlight(false);
+29 −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.draganddrop

import android.view.View
import java.util.function.BiConsumer

interface DragZoneAnimator {
    /**
     * Each consumer will be called for the corresponding DropZoneView.
     * This must match the number of targets in [.mTargets] otherwise will
     * throw an [IllegalStateException]
     */
    fun animateDragTargets(viewsToAnimate: List<BiConsumer<SplitDragPolicy.Target, View>>)
}
 No newline at end of file
+1 −1
Original line number Diff line number Diff line
@@ -47,7 +47,7 @@ interface DropTarget {
    /**
     * Called when user is hovering Drag object over the given Target
     */
    fun onHoveringOver(target: SplitDragPolicy.Target) {}
    fun onHoveringOver(target: SplitDragPolicy.Target?) {}
    /**
     * Called when the user has dropped the provided target (need not be the same target as
     * [onHoveringOver])
+30 −0
Original line number Diff line number Diff line
@@ -20,12 +20,14 @@ import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_

import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Path;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.util.AttributeSet;
import android.util.FloatProperty;
import android.view.Gravity;
@@ -33,6 +35,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.Nullable;

@@ -83,6 +86,7 @@ public class DropZoneView extends FrameLayout {
    private int mTargetBackgroundColor;
    private ObjectAnimator mMarginAnimator;
    private float mMarginPercent;
    private TextView mDebugIndex;

    // Renders a highlight or neutral transparent color
    private ColorDrawable mColorDrawable;
@@ -125,6 +129,22 @@ public class DropZoneView extends FrameLayout {
        mMarginView = new MarginView(context);
        addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));

        if (DEBUG_LAYOUT) {
            mDebugIndex = new TextView(context);
            mDebugIndex.setVisibility(GONE);
            mDebugIndex.setTextColor(Color.YELLOW);
            addView(mDebugIndex, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                    ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.START | Gravity.TOP));

            View borderView = new View(context);
            addView(borderView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT));
            GradientDrawable border = new GradientDrawable();
            border.setShape(GradientDrawable.RECTANGLE);
            border.setStroke(5, Color.RED);
            borderView.setBackground(border);
        }
    }

    public void onThemeChange() {
@@ -236,6 +256,16 @@ public class DropZoneView extends FrameLayout {
        }
    }

    @SuppressLint("SetTextI18n")
    public void setDebugIndex(int index) {
        if (!DEBUG_LAYOUT) {
            return;
        }

        mDebugIndex.setText("Index:\n" + index);
        mDebugIndex.setVisibility(VISIBLE);
    }

    private void animateBackground(int startColor, int endColor) {
        if (DEBUG_LAYOUT) {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
Loading