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

Commit 8653f2ee authored by Mady Mellor's avatar Mady Mellor Committed by Android (Google) Code Review
Browse files

Merge "Drag and drop to split transition" into sc-v2-dev

parents b0a2e9f7 1c0aefd4
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright (C) 2021 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.
  -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
</selector>
 No newline at end of file
+11 −2
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -156,8 +157,16 @@ public abstract class WMShellBaseModule {
    @WMSingleton
    @Provides
    static DragAndDropController provideDragAndDropController(Context context,
            DisplayController displayController, UiEventLogger uiEventLogger) {
        return new DragAndDropController(context, displayController, uiEventLogger);
            DisplayController displayController, UiEventLogger uiEventLogger,
            IconProvider iconProvider, @ShellMainThread ShellExecutor mainExecutor) {
        return new DragAndDropController(context, displayController, uiEventLogger, iconProvider,
                mainExecutor);
    }

    @WMSingleton
    @Provides
    static DragAndDrop provideDragAndDrop(DragAndDropController dragAndDropController) {
        return dragAndDropController.asDragAndDrop();
    }

    @WMSingleton
+34 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.content.res.Configuration;

import com.android.wm.shell.common.annotations.ExternalThread;

/**
 * Interface for telling DragAndDrop stuff.
 */
@ExternalThread
public interface DragAndDrop {

    /** Called when the theme changes. */
    void onThemeChanged();

    /** Called when the configuration changes. */
    void onConfigChanged(Configuration newConfig);
}
+43 −3
Original line number Diff line number Diff line
@@ -41,7 +41,6 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.util.Slog;
import android.util.SparseArray;
import android.view.Display;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
@@ -53,8 +52,10 @@ import android.widget.FrameLayout;
import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;

@@ -71,16 +72,26 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
    private final Context mContext;
    private final DisplayController mDisplayController;
    private final DragAndDropEventLogger mLogger;
    private final IconProvider mIconProvider;
    private SplitScreenController mSplitScreen;
    private ShellExecutor mMainExecutor;
    private DragAndDropImpl mImpl;

    private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
    private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();

    public DragAndDropController(Context context, DisplayController displayController,
            UiEventLogger uiEventLogger) {
            UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) {
        mContext = context;
        mDisplayController = displayController;
        mLogger = new DragAndDropEventLogger(uiEventLogger);
        mIconProvider = iconProvider;
        mMainExecutor = mainExecutor;
        mImpl = new DragAndDropImpl();
    }

    public DragAndDrop asDragAndDrop() {
        return mImpl;
    }

    public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -117,7 +128,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, mSplitScreen);
        DragLayout dragLayout = new DragLayout(context, mSplitScreen, mIconProvider);
        rootView.addView(dragLayout,
                new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        try {
@@ -267,6 +278,18 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
        return mimeTypes;
    }

    private void onThemeChange() {
        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
            mDisplayDropTargets.get(i).dragLayout.onThemeChange();
        }
    }

    private void onConfigChanged(Configuration newConfig) {
        for (int i = 0; i < mDisplayDropTargets.size(); i++) {
            mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
        }
    }

    private static class PerDisplay {
        final int displayId;
        final Context context;
@@ -287,4 +310,21 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
            dragLayout = dl;
        }
    }

    private class DragAndDropImpl implements DragAndDrop {

        @Override
        public void onThemeChanged() {
            mMainExecutor.execute(() -> {
                DragAndDropController.this.onThemeChange();
            });
        }

        @Override
        public void onConfigChanged(Configuration newConfig) {
            mMainExecutor.execute(() -> {
                DragAndDropController.this.onConfigChanged(newConfig);
            });
        }
    }
}
+171 −54
Original line number Diff line number Diff line
@@ -16,78 +16,138 @@

package com.android.wm.shell.draganddrop;

import static com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.wm.shell.animation.Interpolators.LINEAR;
import static com.android.wm.shell.animation.Interpolators.LINEAR_OUT_SLOW_IN;
import static android.app.StatusBarManager.DISABLE_NONE;

import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.content.ClipData;
import android.content.Context;
import android.graphics.Canvas;
import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.view.DragEvent;
import android.view.SurfaceControl;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowInsets.Type;

import androidx.annotation.NonNull;
import android.widget.LinearLayout;

import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;

import java.util.ArrayList;
import java.util.List;

/**
 * Coordinates the visible drop targets for the current drag.
 */
public class DragLayout extends View {
public class DragLayout extends LinearLayout {

    // While dragging the status bar is hidden.
    private static final int HIDE_STATUS_BAR_FLAGS = StatusBarManager.DISABLE_NOTIFICATION_ICONS
            | StatusBarManager.DISABLE_NOTIFICATION_ALERTS
            | StatusBarManager.DISABLE_CLOCK
            | StatusBarManager.DISABLE_SYSTEM_INFO;

    private final DragAndDropPolicy mPolicy;
    private final SplitScreenController mSplitScreenController;
    private final IconProvider mIconProvider;
    private final StatusBarManager mStatusBarManager;

    private DragAndDropPolicy.Target mCurrentTarget = null;
    private DropOutlineDrawable mDropOutline;
    private DropZoneView mDropZoneView1;
    private DropZoneView mDropZoneView2;

    private int mDisplayMargin;
    private Insets mInsets = Insets.NONE;

    private boolean mIsShowing;
    private boolean mHasDropped;

    public DragLayout(Context context, SplitScreenController splitscreen) {
    @SuppressLint("WrongConstant")
    public DragLayout(Context context, SplitScreenController splitScreenController,
            IconProvider iconProvider) {
        super(context);
        mPolicy = new DragAndDropPolicy(context, splitscreen);
        mSplitScreenController = splitScreenController;
        mIconProvider = iconProvider;
        mPolicy = new DragAndDropPolicy(context, splitScreenController);
        mStatusBarManager = context.getSystemService(StatusBarManager.class);

        mDisplayMargin = context.getResources().getDimensionPixelSize(
                R.dimen.drop_layout_display_margin);
        mDropOutline = new DropOutlineDrawable(context);
        setBackground(mDropOutline);
        setWillNotDraw(false);

        mDropZoneView1 = new DropZoneView(context);
        mDropZoneView2 = new DropZoneView(context);
        addView(mDropZoneView1, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        addView(mDropZoneView2, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
                ViewGroup.LayoutParams.MATCH_PARENT));
        ((LayoutParams) mDropZoneView1.getLayoutParams()).weight = 1;
        ((LayoutParams) mDropZoneView2.getLayoutParams()).weight = 1;
        updateContainerMargins();
    }

    @Override
    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
        mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
        recomputeDropTargets();

        final int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            mDropZoneView1.setBottomInset(mInsets.bottom);
            mDropZoneView2.setBottomInset(mInsets.bottom);
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            mDropZoneView1.setBottomInset(0);
            mDropZoneView2.setBottomInset(mInsets.bottom);
        }
        return super.onApplyWindowInsets(insets);
    }

    @Override
    protected boolean verifyDrawable(@NonNull Drawable who) {
        return who == mDropOutline || super.verifyDrawable(who);
    public void onThemeChange() {
        mDropZoneView1.onThemeChange();
        mDropZoneView2.onThemeChange();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (mCurrentTarget != null) {
            mDropOutline.draw(canvas);
    public void onConfigChanged(Configuration newConfig) {
        final int orientation = getResources().getConfiguration().orientation;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE
                && getOrientation() != HORIZONTAL) {
            setOrientation(LinearLayout.HORIZONTAL);
            updateContainerMargins();
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT
                && getOrientation() != VERTICAL) {
            setOrientation(LinearLayout.VERTICAL);
            updateContainerMargins();
        }
    }

    private void updateContainerMargins() {
        final int orientation = getResources().getConfiguration().orientation;
        final float halfMargin = mDisplayMargin / 2f;
        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
            mDropZoneView1.setContainerMargin(
                    mDisplayMargin, mDisplayMargin, halfMargin, mDisplayMargin);
            mDropZoneView2.setContainerMargin(
                    halfMargin, mDisplayMargin, mDisplayMargin, mDisplayMargin);
        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
            mDropZoneView1.setContainerMargin(
                    mDisplayMargin, mDisplayMargin, mDisplayMargin, halfMargin);
            mDropZoneView2.setContainerMargin(
                    mDisplayMargin, halfMargin, mDisplayMargin, mDisplayMargin);
        }
    }

@@ -104,6 +164,43 @@ public class DragLayout extends View {
        mPolicy.start(displayLayout, initialData, loggerSessionId);
        mHasDropped = false;
        mCurrentTarget = null;

        List<ActivityManager.RunningTaskInfo> tasks = null;
        // Figure out the splashscreen info for the existing task(s).
        try {
            tasks = ActivityTaskManager.getService().getTasks(2,
                            false /* filterOnlyVisibleRecents */,
                            false /* keepIntentExtra */);
        } catch (RemoteException e) {
            // don't show an icon / will just use the defaults
        }
        if (tasks != null && !tasks.isEmpty()) {
            ActivityManager.RunningTaskInfo taskInfo1 = tasks.get(0);
            Drawable icon1 = mIconProvider.getIcon(taskInfo1.topActivityInfo);
            int bgColor1 = getResizingBackgroundColor(taskInfo1);

            boolean alreadyInSplit = mSplitScreenController != null
                    && mSplitScreenController.isSplitScreenVisible();
            if (alreadyInSplit && tasks.size() > 1) {
                ActivityManager.RunningTaskInfo taskInfo2 = tasks.get(1);
                Drawable icon2 = mIconProvider.getIcon(taskInfo2.topActivityInfo);
                int bgColor2 = getResizingBackgroundColor(taskInfo2);

                // figure out which task is on which side
                int splitPosition1 = mSplitScreenController.getSplitPosition(taskInfo1.taskId);
                boolean isTask1TopOrLeft = splitPosition1 == SPLIT_POSITION_TOP_OR_LEFT;
                if (isTask1TopOrLeft) {
                    mDropZoneView1.setAppInfo(bgColor1, icon1);
                    mDropZoneView2.setAppInfo(bgColor2, icon2);
                } else {
                    mDropZoneView2.setAppInfo(bgColor1, icon1);
                    mDropZoneView1.setAppInfo(bgColor2, icon2);
                }
            } else {
                mDropZoneView1.setAppInfo(bgColor1, icon1);
                mDropZoneView2.setAppInfo(bgColor1, icon1);
            }
        }
    }

    public void show() {
@@ -139,20 +236,14 @@ public class DragLayout extends View {
            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
            if (target == null) {
                // Animating to no target
                mDropOutline.startVisibilityAnimation(false, LINEAR);
                Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
                finalBounds.inset(mDisplayMargin, mDisplayMargin);
                mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
                animateSplitContainers(false, null /* animCompleteCallback */);
            } else if (mCurrentTarget == null) {
                // Animating to first target
                mDropOutline.startVisibilityAnimation(true, LINEAR);
                Rect initialBounds = new Rect(target.drawRegion);
                initialBounds.inset(mDisplayMargin, mDisplayMargin);
                mDropOutline.setRegionBounds(initialBounds);
                mDropOutline.startBoundsAnimation(target.drawRegion, LINEAR_OUT_SLOW_IN);
                animateSplitContainers(true, null /* animCompleteCallback */);
                animateHighlight(target);
            } else {
                // Bounds change
                mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN);
                // Switching between targets
                animateHighlight(target);
            }
            mCurrentTarget = target;
        }
@@ -163,26 +254,7 @@ public class DragLayout extends View {
     */
    public void hide(DragEvent event, Runnable hideCompleteCallback) {
        mIsShowing = false;
        ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR);
        ObjectAnimator boundsAnimator = null;
        if (mCurrentTarget != null) {
            Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
            finalBounds.inset(mDisplayMargin, mDisplayMargin);
            boundsAnimator = mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
        }

        if (hideCompleteCallback != null) {
            ObjectAnimator lastAnim = boundsAnimator != null
                    ? boundsAnimator
                    : alphaAnimator;
            lastAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    hideCompleteCallback.run();
                }
            });
        }

        animateSplitContainers(false, hideCompleteCallback);
        mCurrentTarget = null;
    }

@@ -201,4 +273,49 @@ public class DragLayout extends View {
        hide(event, dropCompleteCallback);
        return handledDrop;
    }

    private void animateSplitContainers(boolean visible, Runnable animCompleteCallback) {
        mStatusBarManager.disable(visible
                ? HIDE_STATUS_BAR_FLAGS
                : DISABLE_NONE);
        mDropZoneView1.setShowingMargin(visible);
        mDropZoneView2.setShowingMargin(visible);
        ObjectAnimator animator = mDropZoneView1.getAnimator();
        if (animCompleteCallback != null) {
            if (animator != null) {
                animator.addListener(new AnimatorListenerAdapter() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        animCompleteCallback.run();
                    }
                });
            } else {
                // If there's no animator the animation is done so run immediately
                animCompleteCallback.run();
            }
        }
    }

    private void animateHighlight(DragAndDropPolicy.Target target) {
        if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_LEFT
                || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_TOP) {
            mDropZoneView1.setShowingHighlight(true);
            mDropZoneView1.setShowingSplash(false);

            mDropZoneView2.setShowingHighlight(false);
            mDropZoneView2.setShowingSplash(true);
        } else if (target.type == DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT
                || target.type == DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM) {
            mDropZoneView1.setShowingHighlight(false);
            mDropZoneView1.setShowingSplash(true);

            mDropZoneView2.setShowingHighlight(true);
            mDropZoneView2.setShowingSplash(false);
        }
    }

    private static int getResizingBackgroundColor(ActivityManager.RunningTaskInfo taskInfo) {
        final int taskBgColor = taskInfo.taskDescription.getBackgroundColor();
        return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
    }
}
Loading