diff options
| author | 2021-11-30 18:17:14 +0000 | |
|---|---|---|
| committer | 2021-11-30 18:17:14 +0000 | |
| commit | f42d0346ffc8945da543e34127930b24f7a4d4d1 (patch) | |
| tree | a0445e1184faf95b5cbbbd805b08548ca65f7596 | |
| parent | 23ca5b62c95ad0fb443f15e0523e46138c719564 (diff) | |
| parent | 8653f2eeb55fdd3848359d3b41f14934e4924c29 (diff) | |
Merge "Drag and drop to split transition" into sc-v2-dev am: 8653f2eeb5
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/16224193
Change-Id: Ie294b6d276c9d9862869c54c520f9b898df0919c
14 files changed, 643 insertions, 63 deletions
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml new file mode 100644 index 000000000000..329e5b9b31a0 --- /dev/null +++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml @@ -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>
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 6b622044d25d..9500e8aecb57 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -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 diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java new file mode 100644 index 000000000000..edeff6e37182 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java @@ -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); +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java index d2b4711d30af..101295d246bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java @@ -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); + }); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java index efc9ed0f75b2..20d8054f6e90 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java @@ -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(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java new file mode 100644 index 000000000000..2f47af57d496 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java @@ -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); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 262a9a1ba2be..d2e341d3c38c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -188,6 +188,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, && mStageCoordinator.getStageOfTask(taskId) != STAGE_TYPE_UNDEFINED; } + public @SplitPosition int getSplitPosition(int taskId) { + return mStageCoordinator.getSplitPosition(taskId); + } + public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) { return moveToStage(taskId, STAGE_TYPE_SIDE, sideStagePosition, new WindowContainerTransaction()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index a5579ae7dc25..8ad0f200d448 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -678,6 +678,16 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, 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) { opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java index bfa2c92f6679..9f745208d3ed 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java @@ -30,7 +30,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; 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.ShellExecutor; import org.junit.Before; import org.junit.Test; @@ -59,8 +61,8 @@ public class DragAndDropControllerTest { @Before public void setUp() throws RemoteException { MockitoAnnotations.initMocks(this); - - mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger); + mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger, + mock(IconProvider.class), mock(ShellExecutor.class)); } @Test diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java index d37dcafde92a..bb0e79fe23a8 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java @@ -121,7 +121,8 @@ public class SystemUIFactory { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) - .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())); + .setSizeCompatUI(Optional.of(mWMComponent.getSizeCompatUI())) + .setDragAndDrop(Optional.of(mWMComponent.getDragAndDrop())); } else { // TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option // is separating this logic into newly creating SystemUITestsFactory. @@ -140,7 +141,8 @@ public class SystemUIFactory { .setStartingSurface(Optional.ofNullable(null)) .setTaskSurfaceHelper(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) - .setSizeCompatUI(Optional.ofNullable(null)); + .setSizeCompatUI(Optional.ofNullable(null)) + .setDragAndDrop(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); if (mInitializeComponents) { diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 5fdf026b86f3..c5d3a70676d0 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -32,6 +32,7 @@ import com.android.wm.shell.TaskViewFactory; import com.android.wm.shell.apppairs.AppPairs; import com.android.wm.shell.bubbles.Bubbles; 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.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -111,6 +112,9 @@ public interface SysUIComponent { @BindsInstance Builder setSizeCompatUI(Optional<SizeCompatUI> s); + @BindsInstance + Builder setDragAndDrop(Optional<DragAndDrop> d); + SysUIComponent build(); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 543ba8f9b854..90a3ad225f51 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -29,6 +29,7 @@ import com.android.wm.shell.dagger.TvWMShellModule; import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; 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.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -119,4 +120,7 @@ public interface WMComponent { @WMSingleton SizeCompatUI getSizeCompatUI(); + + @WMSingleton + DragAndDrop getDragAndDrop(); } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index a56c177ef9ae..b546edf428b8 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -56,6 +56,7 @@ import com.android.systemui.statusbar.policy.UserInfoController; import com.android.systemui.tracing.ProtoTracer; import com.android.systemui.tracing.nano.SystemUiTraceProto; 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.legacysplitscreen.LegacySplitScreen; 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<ShellCommandHandler> mShellCommandHandler; private final Optional<SizeCompatUI> mSizeCompatUIOptional; + private final Optional<DragAndDrop> mDragAndDropOptional; private final CommandQueue mCommandQueue; private final ConfigurationController mConfigurationController; @@ -142,6 +144,7 @@ public final class WMShell extends SystemUI Optional<HideDisplayCutout> hideDisplayCutoutOptional, Optional<ShellCommandHandler> shellCommandHandler, Optional<SizeCompatUI> sizeCompatUIOptional, + Optional<DragAndDrop> dragAndDropOptional, CommandQueue commandQueue, ConfigurationController configurationController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -167,6 +170,7 @@ public final class WMShell extends SystemUI mProtoTracer = protoTracer; mShellCommandHandler = shellCommandHandler; mSizeCompatUIOptional = sizeCompatUIOptional; + mDragAndDropOptional = dragAndDropOptional; mSysUiMainExecutor = sysUiMainExecutor; } @@ -182,6 +186,7 @@ public final class WMShell extends SystemUI mOneHandedOptional.ifPresent(this::initOneHanded); mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout); mSizeCompatUIOptional.ifPresent(this::initSizeCompatUi); + mDragAndDropOptional.ifPresent(this::initDragAndDrop); } @VisibleForTesting @@ -396,6 +401,20 @@ public final class WMShell extends SystemUI 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 public void writeToProto(SystemUiTraceProto proto) { if (proto.wmShell == null) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java index ae7afcef57a6..1e15d2ae0bdb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java @@ -35,6 +35,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.tracing.ProtoTracer; import com.android.wm.shell.ShellCommandHandler; 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.legacysplitscreen.LegacySplitScreen; import com.android.wm.shell.onehanded.OneHanded; @@ -79,6 +80,7 @@ public class WMShellTest extends SysuiTestCase { @Mock ShellCommandHandler mShellCommandHandler; @Mock SizeCompatUI mSizeCompatUI; @Mock ShellExecutor mSysUiMainExecutor; + @Mock DragAndDrop mDragAndDrop; @Before public void setUp() { @@ -87,6 +89,7 @@ public class WMShellTest extends SysuiTestCase { mWMShell = new WMShell(mContext, Optional.of(mPip), Optional.of(mLegacySplitScreen), Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout), Optional.of(mShellCommandHandler), Optional.of(mSizeCompatUI), + Optional.of(mDragAndDrop), mCommandQueue, mConfigurationController, mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle, mSysUiMainExecutor); |