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

This commit is contained in:
Mady Mellor
2021-11-30 17:50:30 +00:00
committed by Android (Google) Code Review
14 changed files with 643 additions and 63 deletions

View File

@@ -0,0 +1,19 @@
<?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>

View File

@@ -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.common.annotations.ShellSplashscreenThread;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; 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.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener; import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.fullscreen.FullscreenTaskListener;
@@ -156,8 +157,16 @@ public abstract class WMShellBaseModule {
@WMSingleton @WMSingleton
@Provides @Provides
static DragAndDropController provideDragAndDropController(Context context, static DragAndDropController provideDragAndDropController(Context context,
DisplayController displayController, UiEventLogger uiEventLogger) { DisplayController displayController, UiEventLogger uiEventLogger,
return new DragAndDropController(context, displayController, 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 @WMSingleton

View File

@@ -0,0 +1,34 @@
/*
* 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);
}

View File

@@ -41,7 +41,6 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat; import android.graphics.PixelFormat;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import android.view.Display;
import android.view.DragEvent; import android.view.DragEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.SurfaceControl; import android.view.SurfaceControl;
@@ -53,8 +52,10 @@ import android.widget.FrameLayout;
import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceId;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayController; 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.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -71,16 +72,26 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
private final Context mContext; private final Context mContext;
private final DisplayController mDisplayController; private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger; private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
private SplitScreenController mSplitScreen; private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
private DragAndDropImpl mImpl;
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>(); private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
public DragAndDropController(Context context, DisplayController displayController, public DragAndDropController(Context context, DisplayController displayController,
UiEventLogger uiEventLogger) { UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) {
mContext = context; mContext = context;
mDisplayController = displayController; mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger); mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
mMainExecutor = mainExecutor;
mImpl = new DragAndDropImpl();
}
public DragAndDrop asDragAndDrop() {
return mImpl;
} }
public void initialize(Optional<SplitScreenController> splitscreen) { public void initialize(Optional<SplitScreenController> splitscreen) {
@@ -117,7 +128,7 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
R.layout.global_drop_target, null); R.layout.global_drop_target, null);
rootView.setOnDragListener(this); rootView.setOnDragListener(this);
rootView.setVisibility(View.INVISIBLE); rootView.setVisibility(View.INVISIBLE);
DragLayout dragLayout = new DragLayout(context, mSplitScreen); DragLayout dragLayout = new DragLayout(context, mSplitScreen, mIconProvider);
rootView.addView(dragLayout, rootView.addView(dragLayout,
new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
try { try {
@@ -267,6 +278,18 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
return mimeTypes; 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 { private static class PerDisplay {
final int displayId; final int displayId;
final Context context; final Context context;
@@ -287,4 +310,21 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
dragLayout = dl; 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);
});
}
}
} }

View File

@@ -16,78 +16,138 @@
package com.android.wm.shell.draganddrop; package com.android.wm.shell.draganddrop;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN; import static android.app.StatusBarManager.DISABLE_NONE;
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.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.animation.Interpolators.LINEAR_OUT_SLOW_IN;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator; 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.ClipData;
import android.content.Context; import android.content.Context;
import android.graphics.Canvas; import android.content.res.Configuration;
import android.graphics.Color;
import android.graphics.Insets; import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.RemoteException;
import android.view.DragEvent; import android.view.DragEvent;
import android.view.SurfaceControl; import android.view.SurfaceControl;
import android.view.View; import android.view.ViewGroup;
import android.view.WindowInsets; import android.view.WindowInsets;
import android.view.WindowInsets.Type; import android.view.WindowInsets.Type;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog; import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R; import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.protolog.ShellProtoLogGroup; import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.splitscreen.SplitScreenController;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List;
/** /**
* Coordinates the visible drop targets for the current drag. * 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 DragAndDropPolicy mPolicy;
private final SplitScreenController mSplitScreenController;
private final IconProvider mIconProvider;
private final StatusBarManager mStatusBarManager;
private DragAndDropPolicy.Target mCurrentTarget = null; private DragAndDropPolicy.Target mCurrentTarget = null;
private DropOutlineDrawable mDropOutline; private DropZoneView mDropZoneView1;
private DropZoneView mDropZoneView2;
private int mDisplayMargin; private int mDisplayMargin;
private Insets mInsets = Insets.NONE; private Insets mInsets = Insets.NONE;
private boolean mIsShowing; private boolean mIsShowing;
private boolean mHasDropped; private boolean mHasDropped;
public DragLayout(Context context, SplitScreenController splitscreen) { @SuppressLint("WrongConstant")
public DragLayout(Context context, SplitScreenController splitScreenController,
IconProvider iconProvider) {
super(context); 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( mDisplayMargin = context.getResources().getDimensionPixelSize(
R.dimen.drop_layout_display_margin); R.dimen.drop_layout_display_margin);
mDropOutline = new DropOutlineDrawable(context);
setBackground(mDropOutline); mDropZoneView1 = new DropZoneView(context);
setWillNotDraw(false); 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 @Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) { public WindowInsets onApplyWindowInsets(WindowInsets insets) {
mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout()); mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout());
recomputeDropTargets(); 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); return super.onApplyWindowInsets(insets);
} }
@Override public void onThemeChange() {
protected boolean verifyDrawable(@NonNull Drawable who) { mDropZoneView1.onThemeChange();
return who == mDropOutline || super.verifyDrawable(who); mDropZoneView2.onThemeChange();
} }
@Override public void onConfigChanged(Configuration newConfig) {
protected void onDraw(Canvas canvas) { final int orientation = getResources().getConfiguration().orientation;
if (mCurrentTarget != null) { if (orientation == Configuration.ORIENTATION_LANDSCAPE
mDropOutline.draw(canvas); && 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); mPolicy.start(displayLayout, initialData, loggerSessionId);
mHasDropped = false; mHasDropped = false;
mCurrentTarget = null; 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() { public void show() {
@@ -139,20 +236,14 @@ public class DragLayout extends View {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target); ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Current target: %s", target);
if (target == null) { if (target == null) {
// Animating to no target // Animating to no target
mDropOutline.startVisibilityAnimation(false, LINEAR); animateSplitContainers(false, null /* animCompleteCallback */);
Rect finalBounds = new Rect(mCurrentTarget.drawRegion);
finalBounds.inset(mDisplayMargin, mDisplayMargin);
mDropOutline.startBoundsAnimation(finalBounds, FAST_OUT_LINEAR_IN);
} else if (mCurrentTarget == null) { } else if (mCurrentTarget == null) {
// Animating to first target // Animating to first target
mDropOutline.startVisibilityAnimation(true, LINEAR); animateSplitContainers(true, null /* animCompleteCallback */);
Rect initialBounds = new Rect(target.drawRegion); animateHighlight(target);
initialBounds.inset(mDisplayMargin, mDisplayMargin);
mDropOutline.setRegionBounds(initialBounds);
mDropOutline.startBoundsAnimation(target.drawRegion, LINEAR_OUT_SLOW_IN);
} else { } else {
// Bounds change // Switching between targets
mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN); animateHighlight(target);
} }
mCurrentTarget = target; mCurrentTarget = target;
} }
@@ -163,26 +254,7 @@ public class DragLayout extends View {
*/ */
public void hide(DragEvent event, Runnable hideCompleteCallback) { public void hide(DragEvent event, Runnable hideCompleteCallback) {
mIsShowing = false; mIsShowing = false;
ObjectAnimator alphaAnimator = mDropOutline.startVisibilityAnimation(false, LINEAR); animateSplitContainers(false, hideCompleteCallback);
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();
}
});
}
mCurrentTarget = null; mCurrentTarget = null;
} }
@@ -201,4 +273,49 @@ public class DragLayout extends View {
hide(event, dropCompleteCallback); hide(event, dropCompleteCallback);
return handledDrop; 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();
}
} }

View File

@@ -0,0 +1,313 @@
/*
* 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.
*/
package com.android.wm.shell.draganddrop;
import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
import android.animation.ObjectAnimator;
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.util.AttributeSet;
import android.util.FloatProperty;
import android.util.IntProperty;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageView;
import androidx.annotation.Nullable;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.R;
/**
* Renders a drop zone area for items being dragged.
*/
public class DropZoneView extends FrameLayout {
private static final int SPLASHSCREEN_ALPHA_INT = (int) (255 * 0.90f);
private static final int HIGHLIGHT_ALPHA_INT = 255;
private static final int MARGIN_ANIMATION_ENTER_DURATION = 400;
private static final int MARGIN_ANIMATION_EXIT_DURATION = 250;
private static final FloatProperty<DropZoneView> INSETS =
new FloatProperty<DropZoneView>("insets") {
@Override
public void setValue(DropZoneView v, float percent) {
v.setMarginPercent(percent);
}
@Override
public Float get(DropZoneView v) {
return v.getMarginPercent();
}
};
private static final IntProperty<ColorDrawable> SPLASHSCREEN_ALPHA =
new IntProperty<ColorDrawable>("splashscreen") {
@Override
public void setValue(ColorDrawable d, int alpha) {
d.setAlpha(alpha);
}
@Override
public Integer get(ColorDrawable d) {
return d.getAlpha();
}
};
private static final IntProperty<ColorDrawable> HIGHLIGHT_ALPHA =
new IntProperty<ColorDrawable>("highlight") {
@Override
public void setValue(ColorDrawable d, int alpha) {
d.setAlpha(alpha);
}
@Override
public Integer get(ColorDrawable d) {
return d.getAlpha();
}
};
private final Path mPath = new Path();
private final float[] mContainerMargin = new float[4];
private float mCornerRadius;
private float mBottomInset;
private int mMarginColor; // i.e. color used for negative space like the container insets
private int mHighlightColor;
private boolean mShowingHighlight;
private boolean mShowingSplash;
private boolean mShowingMargin;
// TODO: might be more seamless to animate between splash/highlight color instead of 2 separate
private ObjectAnimator mSplashAnimator;
private ObjectAnimator mHighlightAnimator;
private ObjectAnimator mMarginAnimator;
private float mMarginPercent;
// Renders a highlight or neutral transparent color
private ColorDrawable mDropZoneDrawable;
// Renders the translucent splashscreen with the app icon in the middle
private ImageView mSplashScreenView;
private ColorDrawable mSplashBackgroundDrawable;
// Renders the margin / insets around the dropzone container
private MarginView mMarginView;
public DropZoneView(Context context) {
this(context, null);
}
public DropZoneView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public DropZoneView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setContainerMargin(0, 0, 0, 0); // make sure it's populated
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
mMarginColor = getResources().getColor(R.color.taskbar_background);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
mDropZoneDrawable = new ColorDrawable();
mDropZoneDrawable.setColor(mHighlightColor);
mDropZoneDrawable.setAlpha(0);
setBackgroundDrawable(mDropZoneDrawable);
mSplashScreenView = new ImageView(context);
mSplashScreenView.setScaleType(ImageView.ScaleType.CENTER);
mSplashBackgroundDrawable = new ColorDrawable();
mSplashBackgroundDrawable.setColor(Color.WHITE);
mSplashBackgroundDrawable.setAlpha(SPLASHSCREEN_ALPHA_INT);
mSplashScreenView.setBackgroundDrawable(mSplashBackgroundDrawable);
addView(mSplashScreenView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
mSplashScreenView.setAlpha(0f);
mMarginView = new MarginView(context);
addView(mMarginView, new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
}
public void onThemeChange() {
mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(getContext());
mMarginColor = getResources().getColor(R.color.taskbar_background);
mHighlightColor = getResources().getColor(android.R.color.system_accent1_500);
final int alpha = mDropZoneDrawable.getAlpha();
mDropZoneDrawable.setColor(mHighlightColor);
mDropZoneDrawable.setAlpha(alpha);
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the desired margins around the drop zone container when fully showing. */
public void setContainerMargin(float left, float top, float right, float bottom) {
mContainerMargin[0] = left;
mContainerMargin[1] = top;
mContainerMargin[2] = right;
mContainerMargin[3] = bottom;
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the bottom inset so the drop zones are above bottom navigation. */
public void setBottomInset(float bottom) {
mBottomInset = bottom;
((LayoutParams) mSplashScreenView.getLayoutParams()).bottomMargin = (int) bottom;
if (mMarginPercent > 0) {
mMarginView.invalidate();
}
}
/** Sets the color and icon to use for the splashscreen when shown. */
public void setAppInfo(int splashScreenColor, Drawable appIcon) {
mSplashBackgroundDrawable.setColor(splashScreenColor);
mSplashScreenView.setImageDrawable(appIcon);
}
/** @return an active animator for this view if one exists. */
@Nullable
public ObjectAnimator getAnimator() {
if (mMarginAnimator != null && mMarginAnimator.isRunning()) {
return mMarginAnimator;
} else if (mHighlightAnimator != null && mHighlightAnimator.isRunning()) {
return mHighlightAnimator;
} else if (mSplashAnimator != null && mSplashAnimator.isRunning()) {
return mSplashAnimator;
}
return null;
}
/** Animates the splashscreen to show or hide. */
public void setShowingSplash(boolean showingSplash) {
if (mShowingSplash != showingSplash) {
mShowingSplash = showingSplash;
animateSplashToState();
}
}
/** Animates the highlight indicating the zone is hovered on or not. */
public void setShowingHighlight(boolean showingHighlight) {
if (mShowingHighlight != showingHighlight) {
mShowingHighlight = showingHighlight;
animateHighlightToState();
}
}
/** Animates the margins around the drop zone to show or hide. */
public void setShowingMargin(boolean visible) {
if (mShowingMargin != visible) {
mShowingMargin = visible;
animateMarginToState();
}
if (!mShowingMargin) {
setShowingHighlight(false);
setShowingSplash(false);
}
}
private void animateSplashToState() {
if (mSplashAnimator != null) {
mSplashAnimator.cancel();
}
mSplashAnimator = ObjectAnimator.ofInt(mSplashBackgroundDrawable,
SPLASHSCREEN_ALPHA,
mSplashBackgroundDrawable.getAlpha(),
mShowingSplash ? SPLASHSCREEN_ALPHA_INT : 0);
if (!mShowingSplash) {
mSplashAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
mSplashAnimator.start();
mSplashScreenView.animate().alpha(mShowingSplash ? 1f : 0f).start();
}
private void animateHighlightToState() {
if (mHighlightAnimator != null) {
mHighlightAnimator.cancel();
}
mHighlightAnimator = ObjectAnimator.ofInt(mDropZoneDrawable,
HIGHLIGHT_ALPHA,
mDropZoneDrawable.getAlpha(),
mShowingHighlight ? HIGHLIGHT_ALPHA_INT : 0);
if (!mShowingHighlight) {
mHighlightAnimator.setInterpolator(FAST_OUT_SLOW_IN);
}
mHighlightAnimator.start();
}
private void animateMarginToState() {
if (mMarginAnimator != null) {
mMarginAnimator.cancel();
}
mMarginAnimator = ObjectAnimator.ofFloat(this, INSETS,
mMarginPercent,
mShowingMargin ? 1f : 0f);
mMarginAnimator.setInterpolator(FAST_OUT_SLOW_IN);
mMarginAnimator.setDuration(mShowingMargin
? MARGIN_ANIMATION_ENTER_DURATION
: MARGIN_ANIMATION_EXIT_DURATION);
mMarginAnimator.start();
}
private void setMarginPercent(float percent) {
if (percent != mMarginPercent) {
mMarginPercent = percent;
mMarginView.invalidate();
}
}
private float getMarginPercent() {
return mMarginPercent;
}
/** Simple view that draws a rounded rect margin around its contents. **/
private class MarginView extends View {
MarginView(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPath.reset();
mPath.addRoundRect(mContainerMargin[0] * mMarginPercent,
mContainerMargin[1] * mMarginPercent,
getWidth() - (mContainerMargin[2] * mMarginPercent),
getHeight() - (mContainerMargin[3] * mMarginPercent) - mBottomInset,
mCornerRadius * mMarginPercent,
mCornerRadius * mMarginPercent,
Path.Direction.CW);
mPath.setFillType(Path.FillType.INVERSE_EVEN_ODD);
canvas.clipPath(mPath);
canvas.drawColor(mMarginColor);
}
}
}

View File

@@ -188,6 +188,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
&& mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED;
} }
public @SplitPosition int getSplitPosition(int taskId) {
return mStageCoordinator.getSplitPosition(taskId);
}
public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition,
new WindowContainerTransaction()); new WindowContainerTransaction());

View File

@@ -678,6 +678,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
outBottomOrRightBounds.set(mSplitLayout.getBounds2()); outBottomOrRightBounds.set(mSplitLayout.getBounds2());
} }
@SplitPosition
int getSplitPosition(int taskId) {
if (mSideStage.getTopVisibleChildTaskId() == taskId) {
return getSideStagePosition();
} else if (mMainStage.getTopVisibleChildTaskId() == taskId) {
return getMainStagePosition();
}
return SPLIT_POSITION_UNDEFINED;
}
private void addActivityOptions(Bundle opts, StageTaskListener stage) { private void addActivityOptions(Bundle opts, StageTaskListener stage) {
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
} }

View File

@@ -30,7 +30,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.common.DisplayController; import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@@ -59,8 +61,8 @@ public class DragAndDropControllerTest {
@Before @Before
public void setUp() throws RemoteException { public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger,
mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger); mock(IconProvider.class), mock(ShellExecutor.class));
} }
@Test @Test

View File

@@ -121,7 +121,8 @@ public class SystemUIFactory {
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
.setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
.setRecentTasks(mWMComponent.getRecentTasks()) .setRecentTasks(mWMComponent.getRecentTasks())
.setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())); .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI()))
.setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop()));
} else { } else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
// is separating this logic into newly creating SystemUITestsFactory. // is separating this logic into newly creating SystemUITestsFactory.
@@ -140,7 +141,8 @@ public class SystemUIFactory {
.setStartingSurface(Optional.ofNullable(null)) .setStartingSurface(Optional.ofNullable(null))
.setTaskSurfaceHelper(Optional.ofNullable(null)) .setTaskSurfaceHelper(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null))
.setSizeCompatUI(Optional.ofNullable(null)); .setSizeCompatUI(Optional.ofNullable(null))
.setDragAndDrop(Optional.ofNullable(null));
} }
mSysUIComponent = builder.build(); mSysUIComponent = builder.build();
if (mInitializeComponents) { if (mInitializeComponents) {

View File

@@ -32,6 +32,7 @@ import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHanded;
@@ -111,6 +112,9 @@ public interface SysUIComponent {
@BindsInstance @BindsInstance
Builder setSizeCompatUI(Optional<SizeCompatUI> s); Builder setSizeCompatUI(Optional<SizeCompatUI> s);
@BindsInstance
Builder setDragAndDrop(Optional<DragAndDrop> d);
SysUIComponent build(); SysUIComponent build();
} }

View File

@@ -29,6 +29,7 @@ import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHanded;
@@ -119,4 +120,7 @@ public interface WMComponent {
@WMSingleton @WMSingleton
SizeCompatUI getSizeCompatUI(); SizeCompatUI getSizeCompatUI();
@WMSingleton
DragAndDrop getDragAndDrop();
} }

View File

@@ -56,6 +56,7 @@ import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto; import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.nano.WmShellTraceProto; import com.android.wm.shell.nano.WmShellTraceProto;
@@ -114,6 +115,7 @@ public final class WMShell extends SystemUI
private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional; private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
private final Optional<ShellCommandHandler> mShellCommandHandler; private final Optional<ShellCommandHandler> mShellCommandHandler;
private final Optional<SizeCompatUI> mSizeCompatUIOptional; private final Optional<SizeCompatUI> mSizeCompatUIOptional;
private final Optional<DragAndDrop> mDragAndDropOptional;
private final CommandQueue mCommandQueue; private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController; private final ConfigurationController mConfigurationController;
@@ -142,6 +144,7 @@ public final class WMShell extends SystemUI
Optional<HideDisplayCutout> hideDisplayCutoutOptional, Optional<HideDisplayCutout> hideDisplayCutoutOptional,
Optional<ShellCommandHandler> shellCommandHandler, Optional<ShellCommandHandler> shellCommandHandler,
Optional<SizeCompatUI> sizeCompatUIOptional, Optional<SizeCompatUI> sizeCompatUIOptional,
Optional<DragAndDrop> dragAndDropOptional,
CommandQueue commandQueue, CommandQueue commandQueue,
ConfigurationController configurationController, ConfigurationController configurationController,
KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -167,6 +170,7 @@ public final class WMShell extends SystemUI
mProtoTracer = protoTracer; mProtoTracer = protoTracer;
mShellCommandHandler = shellCommandHandler; mShellCommandHandler = shellCommandHandler;
mSizeCompatUIOptional = sizeCompatUIOptional; mSizeCompatUIOptional = sizeCompatUIOptional;
mDragAndDropOptional = dragAndDropOptional;
mSysUiMainExecutor = sysUiMainExecutor; mSysUiMainExecutor = sysUiMainExecutor;
} }
@@ -182,6 +186,7 @@ public final class WMShell extends SystemUI
mOneHandedOptional.ifPresent(this::initOneHanded); mOneHandedOptional.ifPresent(this::initOneHanded);
mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi); mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi);
mDragAndDropOptional.ifPresent(this::initDragAndDrop);
} }
@VisibleForTesting @VisibleForTesting
@@ -396,6 +401,20 @@ public final class WMShell extends SystemUI
mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback); mKeyguardUpdateMonitor.registerCallback(mSizeCompatUIKeyguardCallback);
} }
void initDragAndDrop(DragAndDrop dragAndDrop) {
mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
@Override
public void onConfigChanged(Configuration newConfig) {
dragAndDrop.onConfigChanged(newConfig);
}
@Override
public void onThemeChanged() {
dragAndDrop.onThemeChanged();
}
});
}
@Override @Override
public void writeToProto(SystemUiTraceProto proto) { public void writeToProto(SystemUiTraceProto proto) {
if (proto.wmShell == null) { if (proto.wmShell == null) {

View File

@@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.ProtoTracer;
import com.android.wm.shell.ShellCommandHandler; import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.onehanded.OneHanded;
@@ -79,6 +80,7 @@ public class WMShellTest extends SysuiTestCase {
@Mock ShellCommandHandler mShellCommandHandler; @Mock ShellCommandHandler mShellCommandHandler;
@Mock SizeCompatUI mSizeCompatUI; @Mock SizeCompatUI mSizeCompatUI;
@Mock ShellExecutor mSysUiMainExecutor; @Mock ShellExecutor mSysUiMainExecutor;
@Mock DragAndDrop mDragAndDrop;
@Before @Before
public void setUp() { public void setUp() {
@@ -87,6 +89,7 @@ public class WMShellTest extends SysuiTestCase {
mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen), mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen),
Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI), Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI),
Optional.of(mDragAndDrop),
mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor, mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor,
mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer, mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer,
mWakefulnessLifecycle, mSysUiMainExecutor); mWakefulnessLifecycle, mSysUiMainExecutor);