| /* |
| * 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 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 android.animation.Animator; |
| import android.animation.AnimatorListenerAdapter; |
| import android.animation.ObjectAnimator; |
| import android.content.ClipData; |
| import android.content.Context; |
| import android.graphics.Canvas; |
| import android.graphics.Insets; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.view.DragEvent; |
| import android.view.SurfaceControl; |
| import android.view.View; |
| import android.view.WindowInsets; |
| import android.view.WindowInsets.Type; |
| |
| import androidx.annotation.NonNull; |
| |
| import com.android.internal.protolog.common.ProtoLog; |
| 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; |
| |
| /** |
| * Coordinates the visible drop targets for the current drag. |
| */ |
| public class DragLayout extends View { |
| |
| private final DragAndDropPolicy mPolicy; |
| |
| private DragAndDropPolicy.Target mCurrentTarget = null; |
| private DropOutlineDrawable mDropOutline; |
| private int mDisplayMargin; |
| private Insets mInsets = Insets.NONE; |
| |
| private boolean mIsShowing; |
| private boolean mHasDropped; |
| |
| public DragLayout(Context context, SplitScreenController splitscreen) { |
| super(context); |
| mPolicy = new DragAndDropPolicy(context, splitscreen); |
| mDisplayMargin = context.getResources().getDimensionPixelSize( |
| R.dimen.drop_layout_display_margin); |
| mDropOutline = new DropOutlineDrawable(context); |
| setBackground(mDropOutline); |
| setWillNotDraw(false); |
| } |
| |
| @Override |
| public WindowInsets onApplyWindowInsets(WindowInsets insets) { |
| mInsets = insets.getInsets(Type.systemBars() | Type.displayCutout()); |
| recomputeDropTargets(); |
| return super.onApplyWindowInsets(insets); |
| } |
| |
| @Override |
| protected boolean verifyDrawable(@NonNull Drawable who) { |
| return who == mDropOutline || super.verifyDrawable(who); |
| } |
| |
| @Override |
| protected void onDraw(Canvas canvas) { |
| if (mCurrentTarget != null) { |
| mDropOutline.draw(canvas); |
| } |
| } |
| |
| public boolean hasDropTarget() { |
| return mCurrentTarget != null; |
| } |
| |
| public boolean hasDropped() { |
| return mHasDropped; |
| } |
| |
| public void prepare(DisplayLayout displayLayout, ClipData initialData) { |
| mPolicy.start(displayLayout, initialData); |
| mHasDropped = false; |
| mCurrentTarget = null; |
| } |
| |
| public void show() { |
| mIsShowing = true; |
| recomputeDropTargets(); |
| } |
| |
| /** |
| * Recalculates the drop targets based on the current policy. |
| */ |
| private void recomputeDropTargets() { |
| if (!mIsShowing) { |
| return; |
| } |
| final ArrayList<DragAndDropPolicy.Target> targets = mPolicy.getTargets(mInsets); |
| for (int i = 0; i < targets.size(); i++) { |
| final DragAndDropPolicy.Target target = targets.get(i); |
| ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Add target: %s", target); |
| // Inset the draw region by a little bit |
| target.drawRegion.inset(mDisplayMargin, mDisplayMargin); |
| } |
| } |
| |
| /** |
| * Updates the visible drop target as the user drags. |
| */ |
| public void update(DragEvent event) { |
| // Find containing region, if the same as mCurrentRegion, then skip, otherwise, animate the |
| // visibility of the current region |
| DragAndDropPolicy.Target target = mPolicy.getTargetAtLocation( |
| (int) event.getX(), (int) event.getY()); |
| if (mCurrentTarget != target) { |
| 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); |
| } 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); |
| } else { |
| // Bounds change |
| mDropOutline.startBoundsAnimation(target.drawRegion, FAST_OUT_SLOW_IN); |
| } |
| mCurrentTarget = target; |
| } |
| } |
| |
| /** |
| * Hides the drag layout and animates out the visible drop targets. |
| */ |
| 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(); |
| } |
| }); |
| } |
| |
| mCurrentTarget = null; |
| } |
| |
| /** |
| * Handles the drop onto a target and animates out the visible drop targets. |
| */ |
| public boolean drop(DragEvent event, SurfaceControl dragSurface, |
| Runnable dropCompleteCallback) { |
| final boolean handledDrop = mCurrentTarget != null; |
| mHasDropped = true; |
| |
| // Process the drop |
| mPolicy.handleDrop(mCurrentTarget, event.getClipData()); |
| |
| // TODO(b/169894807): Coordinate with dragSurface |
| hide(event, dropCompleteCallback); |
| return handledDrop; |
| } |
| } |