blob: b3423362347fd12983609506f0f48ee5d825aa9d [file] [log] [blame]
/*
* 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;
}
}