Add ListenerView so that we can fast finish the FloatingIconView fade anim.
am: de0093dfa5

Change-Id: I9d7edb00eee2fc45bfc7a79421fa33fcb955b868
diff --git a/src/com/android/launcher3/AbstractFloatingView.java b/src/com/android/launcher3/AbstractFloatingView.java
index 0e08276..3cb6ba6 100644
--- a/src/com/android/launcher3/AbstractFloatingView.java
+++ b/src/com/android/launcher3/AbstractFloatingView.java
@@ -30,6 +30,7 @@
 import android.util.Pair;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.LinearLayout;
 
 import androidx.annotation.IntDef;
@@ -58,6 +59,7 @@
             TYPE_ON_BOARD_POPUP,
             TYPE_DISCOVERY_BOUNCE,
             TYPE_SNACKBAR,
+            TYPE_LISTENER,
 
             TYPE_TASK_MENU,
             TYPE_OPTIONS_POPUP
@@ -72,15 +74,16 @@
     public static final int TYPE_ON_BOARD_POPUP = 1 << 5;
     public static final int TYPE_DISCOVERY_BOUNCE = 1 << 6;
     public static final int TYPE_SNACKBAR = 1 << 7;
+    public static final int TYPE_LISTENER = 1 << 8;
 
     // Popups related to quickstep UI
-    public static final int TYPE_TASK_MENU = 1 << 8;
-    public static final int TYPE_OPTIONS_POPUP = 1 << 9;
+    public static final int TYPE_TASK_MENU = 1 << 9;
+    public static final int TYPE_OPTIONS_POPUP = 1 << 10;
 
     public static final int TYPE_ALL = TYPE_FOLDER | TYPE_ACTION_POPUP
             | TYPE_WIDGETS_BOTTOM_SHEET | TYPE_WIDGET_RESIZE_FRAME | TYPE_WIDGETS_FULL_SHEET
             | TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE | TYPE_TASK_MENU
-            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR;
+            | TYPE_OPTIONS_POPUP | TYPE_SNACKBAR | TYPE_LISTENER;
 
     // Type of popups which should be kept open during launcher rebind
     public static final int TYPE_REBIND_SAFE = TYPE_WIDGETS_FULL_SHEET
@@ -90,7 +93,7 @@
     public static final int TYPE_HIDE_BACK_BUTTON = TYPE_ON_BOARD_POPUP | TYPE_DISCOVERY_BOUNCE
             | TYPE_SNACKBAR;
 
-    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE;
+    public static final int TYPE_ACCESSIBLE = TYPE_ALL & ~TYPE_DISCOVERY_BOUNCE & ~TYPE_LISTENER;
 
     // These view all have particular operation associated with swipe down interaction.
     public static final int TYPE_STATUS_BAR_SWIPE_DOWN_DISALLOW = TYPE_WIDGETS_BOTTOM_SHEET |
diff --git a/src/com/android/launcher3/views/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index f96652e..f2fc718 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -70,7 +70,7 @@
 public class FloatingIconView extends View implements Animator.AnimatorListener, ClipPathView {
 
     public static final float SHAPE_PROGRESS_DURATION = 0.15f;
-
+    private static final int FADE_DURATION_MS = 200;
     private static final Rect sTmpRect = new Rect();
 
     private Runnable mEndRunnable;
@@ -93,10 +93,15 @@
     private float mBgDrawableStartScale = 1f;
     private float mBgDrawableEndScale = 1f;
 
+    private AnimatorSet mFadeAnimatorSet;
+    private ListenerView mListenerView;
+
     private FloatingIconView(Context context) {
         super(context);
+
         mBlurSizeOutline = context.getResources().getDimensionPixelSize(
                 R.dimen.blur_size_medium_outline);
+        mListenerView = new ListenerView(context, null);
     }
 
     /**
@@ -138,6 +143,12 @@
             if (mRevealAnimator == null) {
                 mRevealAnimator = (ValueAnimator) FolderShape.getShape().createRevealAnimator(this,
                         mStartRevealRect, mEndRevealRect, mTaskCornerRadius / scale, !isOpening);
+                mRevealAnimator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        mRevealAnimator = null;
+                    }
+                });
                 mRevealAnimator.start();
                 // We pause here so we can set the current fraction ourselves.
                 mRevealAnimator.pause();
@@ -314,7 +325,7 @@
 
     @WorkerThread
     private int getOffsetForIconBounds(Drawable drawable) {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O ||
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O ||
                 !(drawable instanceof AdaptiveIconDrawable)) {
             return 0;
         }
@@ -364,6 +375,18 @@
         }
     }
 
+    public void onListenerViewClosed() {
+        // Fast finish here.
+        if (mEndRunnable != null) {
+            mEndRunnable.run();
+            mEndRunnable = null;
+        }
+        if (mFadeAnimatorSet != null) {
+            mFadeAnimatorSet.end();
+            mFadeAnimatorSet = null;
+        }
+    }
+
     @Override
     public void onAnimationStart(Animator animator) {}
 
@@ -410,54 +433,71 @@
         // We need to add it to the overlay, but keep it invisible until animation starts..
         final DragLayer dragLayer = launcher.getDragLayer();
         view.setVisibility(INVISIBLE);
-        ((ViewGroup) dragLayer.getParent()).getOverlay().add(view);
+        ((ViewGroup) dragLayer.getParent()).addView(view);
+        dragLayer.addView(view.mListenerView);
+        view.mListenerView.setListener(view::onListenerViewClosed);
 
-        if (hideOriginal) {
-            view.mEndRunnable = () -> {
-                AnimatorSet fade = new AnimatorSet();
-                fade.setDuration(200);
-                fade.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationStart(Animator animation) {
-                        originalView.setVisibility(VISIBLE);
-                    }
+        view.mEndRunnable = () -> {
+            view.mEndRunnable = null;
 
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        ((ViewGroup) dragLayer.getParent()).getOverlay().remove(view);
-
-                        if (view.mRevealAnimator != null) {
-                            view.mRevealAnimator.end();
-                        }
-                    }
-                });
-
-                if (originalView instanceof FolderIcon) {
-                    FolderIcon folderIcon = (FolderIcon) originalView;
-                    folderIcon.setBackgroundVisible(false);
-                    folderIcon.getFolderName().setTextVisibility(false);
-                    fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
-                    fade.addListener(new AnimatorListenerAdapter() {
-                        @Override
-                        public void onAnimationEnd(Animator animation) {
-                            folderIcon.setBackgroundVisible(true);
-                            folderIcon.animateBgShadowAndStroke();
-                            if (folderIcon.hasDot()) {
-                                folderIcon.animateDotScale(0, 1f);
-                            }
-                        }
-                    });
+            if (hideOriginal) {
+                if (isOpening) {
+                    originalView.setVisibility(VISIBLE);
+                    view.finish(dragLayer);
                 } else {
-                    fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+                    view.mFadeAnimatorSet = view.createFadeAnimation(originalView, dragLayer);
+                    view.mFadeAnimatorSet.start();
                 }
-                fade.start();
-                // TODO: Do not run fade animation until we fix b/129421279.
-                fade.end();
-            };
-        }
+            } else {
+                view.finish(dragLayer);
+            }
+        };
         return view;
     }
 
+    private AnimatorSet createFadeAnimation(View originalView, DragLayer dragLayer) {
+        AnimatorSet fade = new AnimatorSet();
+        fade.setDuration(FADE_DURATION_MS);
+        fade.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                originalView.setVisibility(VISIBLE);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finish(dragLayer);
+            }
+        });
+
+        if (originalView instanceof FolderIcon) {
+            FolderIcon folderIcon = (FolderIcon) originalView;
+            folderIcon.setBackgroundVisible(false);
+            folderIcon.getFolderName().setTextVisibility(false);
+            fade.play(folderIcon.getFolderName().createTextAlphaAnimator(true));
+            fade.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    folderIcon.setBackgroundVisible(true);
+                    folderIcon.animateBgShadowAndStroke();
+                    if (folderIcon.hasDot()) {
+                        folderIcon.animateDotScale(0, 1f);
+                    }
+                }
+            });
+        } else {
+            fade.play(ObjectAnimator.ofFloat(originalView, ALPHA, 0f, 1f));
+        }
+
+        return fade;
+    }
+
+    private void finish(DragLayer dragLayer) {
+        ((ViewGroup) dragLayer.getParent()).removeView(this);
+        dragLayer.removeView(mListenerView);
+        recycle();
+    }
+
     private void recycle() {
         setTranslationX(0);
         setTranslationY(0);
@@ -475,10 +515,15 @@
         mBackground = null;
         mClipPath = null;
         mFinalDrawableBounds.setEmpty();
-        mBgDrawableBounds.setEmpty();;
+        mBgDrawableBounds.setEmpty();
         if (mRevealAnimator != null) {
             mRevealAnimator.cancel();
         }
         mRevealAnimator = null;
+        if (mFadeAnimatorSet != null) {
+            mFadeAnimatorSet.cancel();
+        }
+        mFadeAnimatorSet = null;
+        mListenerView.setListener(null);
     }
 }
diff --git a/src/com/android/launcher3/views/ListenerView.java b/src/com/android/launcher3/views/ListenerView.java
new file mode 100644
index 0000000..263f7c4
--- /dev/null
+++ b/src/com/android/launcher3/views/ListenerView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2019 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.launcher3.views;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.launcher3.AbstractFloatingView;
+
+/**
+ * An invisible AbstractFloatingView that can run a callback when it is being closed.
+ */
+public class ListenerView extends AbstractFloatingView {
+
+    public Runnable mCloseListener;
+
+    public ListenerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        setVisibility(View.GONE);
+    }
+
+    public void setListener(Runnable listener) {
+        mCloseListener = listener;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mIsOpen = true;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mIsOpen = false;
+    }
+
+    @Override
+    protected void handleClose(boolean animate) {
+        if (mIsOpen) {
+            if (mCloseListener != null) {
+                mCloseListener.run();
+            } else {
+                if (getParent() instanceof ViewGroup) {
+                    ((ViewGroup) getParent()).removeView(this);
+                }
+            }
+        }
+        mIsOpen = false;
+    }
+
+    @Override
+    public void logActionCommand(int command) {
+        // Users do not interact with FloatingIconView, so there is nothing to log here.
+    }
+
+    @Override
+    protected boolean isOfType(int type) {
+        return (type & TYPE_LISTENER) != 0;
+    }
+
+    @Override
+    public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            handleClose(false);
+        }
+        // We want other views to be able to intercept the touch so we return false here.
+        return false;
+    }
+}