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

Commit 446b9ea7 authored by Vinit Nayak's avatar Vinit Nayak
Browse files

Add support for dragging third app on top of 2 app split

* Lots of UI polish TODO
* Dropping doesn't work, this is just to add plumbing to
support UI for views

Bug: 349828130
Flag: com.android.wm.shell.enable_flexible_split
Test: Things work fine when flag is off

Change-Id: Icdbbef013f054996d9c26dfaeaa22f534210e44a
parent c09e63ed
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