Merge "Add basic return animation when drag is canceled"
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
index e152915..435eae4 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java
@@ -15,6 +15,11 @@
  */
 package com.android.launcher3.taskbar;
 
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.Intent;
@@ -27,7 +32,9 @@
 import android.os.UserHandle;
 import android.view.DragEvent;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import android.view.View;
+import android.view.ViewRootImpl;
 
 import androidx.annotation.Nullable;
 
@@ -39,20 +46,25 @@
 import com.android.launcher3.DropTarget;
 import com.android.launcher3.LauncherSettings;
 import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
 import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.anim.Interpolators;
 import com.android.launcher3.config.FeatureFlags;
 import com.android.launcher3.dragndrop.DragController;
 import com.android.launcher3.dragndrop.DragDriver;
 import com.android.launcher3.dragndrop.DragOptions;
 import com.android.launcher3.dragndrop.DragView;
 import com.android.launcher3.dragndrop.DraggableView;
+import com.android.launcher3.folder.FolderIcon;
 import com.android.launcher3.graphics.DragPreviewProvider;
 import com.android.launcher3.logging.StatsLogManager;
+import com.android.launcher3.model.data.FolderInfo;
 import com.android.launcher3.model.data.ItemInfo;
 import com.android.launcher3.model.data.WorkspaceItemInfo;
 import com.android.launcher3.popup.PopupContainerWithArrow;
 import com.android.launcher3.shortcuts.DeepShortcutView;
 import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
+import com.android.launcher3.util.LauncherBindableItemsContainer;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.system.ClipDescriptionCompat;
 import com.android.systemui.shared.system.LauncherAppsCompat;
@@ -66,6 +78,8 @@
 public class TaskbarDragController extends DragController<TaskbarActivityContext> implements
         TaskbarControllers.LoggableTaskbarController {
 
+    private static boolean DEBUG_DRAG_SHADOW_SURFACE = false;
+
     private final int mDragIconSize;
     private final int[] mTempXY = new int[2];
 
@@ -78,6 +92,9 @@
 
     private boolean mIsSystemDragInProgress;
 
+    // Animation for the drag shadow back into position after an unsuccessful drag
+    private ValueAnimator mReturnAnimator;
+
     public TaskbarDragController(TaskbarActivityContext activity) {
         super(activity);
         Resources resources = mActivity.getResources();
@@ -279,6 +296,9 @@
             @Override
             public void onDrawShadow(Canvas canvas) {
                 canvas.save();
+                if (DEBUG_DRAG_SHADOW_SURFACE) {
+                    canvas.drawColor(0xffff0000);
+                }
                 float scale = mDragObject.dragView.getScaleX();
                 canvas.scale(scale, scale);
                 mDragObject.dragView.draw(canvas);
@@ -337,8 +357,9 @@
 
             ClipData clipData = new ClipData(clipDescription, new ClipData.Item(intent));
             if (btv.startDragAndDrop(clipData, shadowBuilder, null /* localState */,
-                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE)) {
-                onSystemDragStarted();
+                    View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_OPAQUE
+                            | View.DRAG_FLAG_REQUEST_SURFACE_FOR_RETURN_ANIMATION)) {
+                onSystemDragStarted(btv);
 
                 mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
                         .withInstanceId(launcherInstanceId)
@@ -347,7 +368,7 @@
         }
     }
 
-    private void onSystemDragStarted() {
+    private void onSystemDragStarted(BubbleTextView btv) {
         mIsSystemDragInProgress = true;
         mActivity.getDragLayer().setOnDragListener((view, dragEvent) -> {
             switch (dragEvent.getAction()) {
@@ -356,7 +377,12 @@
                     return true;
                 case DragEvent.ACTION_DRAG_ENDED:
                     mIsSystemDragInProgress = false;
-                    maybeOnDragEnd();
+                    if (dragEvent.getResult()) {
+                        maybeOnDragEnd();
+                    } else {
+                        // This will take care of calling maybeOnDragEnd() after the animation
+                        animateGlobalDragViewToOriginalPosition(btv, dragEvent);
+                    }
                     return true;
             }
             return false;
@@ -382,6 +408,93 @@
         maybeOnDragEnd();
     }
 
+    private void animateGlobalDragViewToOriginalPosition(BubbleTextView btv,
+            DragEvent dragEvent) {
+        SurfaceControl dragSurface = dragEvent.getDragSurface();
+
+        // For top level icons, the target is the icon itself
+        View target = btv;
+        Object tag = btv.getTag();
+        if (tag instanceof ItemInfo) {
+            ItemInfo item = (ItemInfo) tag;
+            TaskbarViewController taskbarViewController = mControllers.taskbarViewController;
+            if (item.container == CONTAINER_ALL_APPS) {
+                // Since all apps closes when the drag starts, target the all apps button instead
+                target = taskbarViewController.getAllAppsButtonView();
+            } else if (item.container >= 0) {
+                // Since folders close when the drag starts, target the folder icon instead
+                LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
+                    if (info instanceof FolderInfo && v instanceof FolderIcon) {
+                        FolderInfo fi = (FolderInfo) info;
+                        for (WorkspaceItemInfo si : fi.contents) {
+                            if (si.id == item.id) {
+                                // Found the parent
+                                return true;
+                            }
+                        }
+                    }
+                    return false;
+                };
+                target = taskbarViewController.mapOverItems(op);
+            }
+        }
+
+        // Finish any pending return animation before starting a new drag
+        if (mReturnAnimator != null) {
+            mReturnAnimator.end();
+        }
+
+        float fromX = dragEvent.getX() - dragEvent.getOffsetX();
+        float fromY = dragEvent.getY() - dragEvent.getOffsetY();
+        int[] toPosition = target.getLocationOnScreen();
+        float toScale = (float) target.getWidth() / mDragIconSize;
+        float toAlpha = (target == btv) ? 1f : 0f;
+        final ViewRootImpl viewRoot = target.getViewRootImpl();
+        SurfaceControl.Transaction tx = new SurfaceControl.Transaction();
+        mReturnAnimator = ValueAnimator.ofFloat(0f, 1f);
+        mReturnAnimator.setDuration(300);
+        mReturnAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+        mReturnAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+            @Override
+            public void onAnimationUpdate(ValueAnimator animation) {
+                float t = animation.getAnimatedFraction();
+                float accelT = Interpolators.ACCEL_2.getInterpolation(t);
+                float scale = 1f - t * (1f - toScale);
+                float alpha = 1f - accelT * (1f - toAlpha);
+                tx.setPosition(dragSurface, Utilities.mapRange(t, fromX, toPosition[0]),
+                        Utilities.mapRange(t, fromY, toPosition[1]));
+                tx.setScale(dragSurface, scale, scale);
+                tx.setAlpha(dragSurface, alpha);
+                tx.apply();
+            }
+        });
+        mReturnAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                cleanUpSurface();
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                cleanUpSurface();
+            }
+
+            private void cleanUpSurface() {
+                maybeOnDragEnd();
+                // Synchronize removing the drag surface with the next draw after calling
+                // maybeOnDragEnd()
+                viewRoot.consumeNextDraw((transaction) -> {
+                    transaction.remove(dragSurface);
+                    transaction.apply();
+                    tx.close();
+                });
+                viewRoot.getView().invalidate();
+                mReturnAnimator = null;
+            }
+        });
+        mReturnAnimator.start();
+    }
+
     @Override
     protected float getX(MotionEvent ev) {
         // We will resize to fill the screen while dragging, so use screen coordinates. This ensures
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
index 40c67ff..ade58a9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java
@@ -318,6 +318,13 @@
         return icons;
     }
 
+    /**
+     * Returns the all apps button in the taskbar.
+     */
+    public View getAllAppsButtonView() {
+        return mAllAppsButton;
+    }
+
     // FolderIconParent implemented methods.
 
     @Override
@@ -359,13 +366,18 @@
         return getVisibility() == VISIBLE;
     }
 
-    protected void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
+    /**
+     * Maps {@code op} over all the child views, returning the view that {@code op} evaluates
+     * {@code true} for, or {@code null} if none satisfy {@code op}.
+     */
+    protected View mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
         // map over all the shortcuts on the taskbar
         for (int i = 0; i < getChildCount(); i++) {
             View item = getChildAt(i);
             if (op.evaluate((ItemInfo) item.getTag(), item)) {
-                return;
+                return item;
             }
         }
+        return null;
     }
 }
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 30bdce1..62f1fa5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -164,6 +164,10 @@
         return mTaskbarView.getIconViews();
     }
 
+    public View getAllAppsButtonView() {
+        return mTaskbarView.getAllAppsButtonView();
+    }
+
     public AnimatedFloat getTaskbarIconScaleForStash() {
         return mTaskbarIconScaleForStash;
     }
@@ -270,8 +274,8 @@
         mTaskbarNavButtonTranslationY.updateValue(-deviceProfile.getTaskbarOffsetY());
     }
 
-    public void mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
-        mTaskbarView.mapOverItems(op);
+    public View mapOverItems(LauncherBindableItemsContainer.ItemOperator op) {
+        return mTaskbarView.mapOverItems(op);
     }
 
     /**